﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using System.Security.Principal;

namespace NBusiness.Data.Rules
{
    [Serializable]
    public class AuthorizationRuleCollection : CollectionBase, IList<AuthorizationRule>
    {
        private Hashtable _initializedTypes = new Hashtable();

        private void EnsureRulesInitialized(Type t)
        {
            if (!_initializedTypes.ContainsKey(t))
            {
                lock (_initializedTypes)
                {
                    if (!_initializedTypes.ContainsKey(t))
                    {
                        if (t.IsPublic &&
                            typeof(ModelBase).IsAssignableFrom(t) &&
                            t.GetConstructor(System.Type.EmptyTypes) != null)
                        {
                            //Forces static constructors to be called and rules to be
                            //initialized.
                            ModelBase obj = Activator.CreateInstance(t) as ModelBase;
                            _initializedTypes.Add(t, true);
                        }
                    }
                }
            }
        }

        #region Authorization Checks
        public bool CanFetch(Type type, IPrincipal principal)
        {
            EnsureRulesInitialized(type);
            return CheckAuthorization(type, AuthorizationRuleType.Fetch, principal);
        }

        public bool CanInsert(Type type, IPrincipal principal)
        {
            EnsureRulesInitialized(type);
            return CheckAuthorization(type, AuthorizationRuleType.Fetch, principal);
        }

        public bool CanDelete(Type type, IPrincipal principal)
        {
            EnsureRulesInitialized(type);
            return CheckAuthorization(type, AuthorizationRuleType.Fetch, principal);
        }

        public bool CanUpdate(Type type, IPrincipal principal)
        {
            EnsureRulesInitialized(type);
            return CheckAuthorization(type, AuthorizationRuleType.Fetch, principal);
        }

        private bool CheckAuthorization(Type type, AuthorizationRuleType authType, IPrincipal principal)
        {
            //default is false, but a developer can override the default if they don't
            //want to use the built in authorization features.
            bool ok = (bool)Settings.Instance.GetSetting(SettingsType.AuthorizeDefault, type);

            foreach (AuthorizationRule rule in this.List)
            {
                if (rule.Type == type && rule.RuleType.Equals(authType))
                {
                    //Applies to all users
                    if (rule.Role == "*")
                    {
                        ok = rule.Allow;
                    }
                    //If user is authenticated at least but in any role
                    else if (rule.Role == "?")
                    {
                        ok = rule.Allow && principal.Identity.IsAuthenticated;
                    }
                    else if (principal.IsInRole(rule.Role))
                    {
                        //If explicitly unauthorized, you may not perform this action
                        if (!rule.Allow) return false;

                        //if specifically authorized, you may perform this action
                        //unless another rule has you as specifically unauthorized later.
                        else ok = true;
                    }
                }
            }

            return ok;
        }
        #endregion

        #region IList<AccessRule> Members
        public AuthorizationRule this[int index]
        {
            get
            {
                return (AuthorizationRule)List[index];
            }
            set
            {
                List[index] = value;
            }
        }

        public void Add(AuthorizationRule item)
        {
            this.List.Add(item);
        }

        public void CopyTo(AuthorizationRule[] array, int arrayIndex)
        {
            (this as ICollection).CopyTo(array, arrayIndex);
        }

        public bool Contains(AuthorizationRule item)
        {
            return List.Contains(item);
        }

        public void Insert(int index, AuthorizationRule item)
        {
            List.Insert(index, item);
        }

        public void Remove(AuthorizationRule item)
        {
            List.Remove(item);
        }

        public int IndexOf(AuthorizationRule item)
        {
            return List.IndexOf(item);
        }
        #endregion

        #region ICollection<AuthorizationRule> Members

        public bool IsReadOnly
        {
            get { return false; }
        }

        bool ICollection<AuthorizationRule>.Remove(AuthorizationRule item)
        {
            bool removed = false;
            if (List.Contains(item))
            {
                List.Remove(item);
                removed = true;
            }
            return removed;
        }

        #endregion

        #region IEnumerable<AuthorizationRule> Members

        public new IEnumerator<AuthorizationRule> GetEnumerator()
        {
            foreach (AuthorizationRule rule in List)
            {
                yield return rule;
            }
        }

        #endregion
    }
}
